package com.imooc.xunwu.service.search;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.primitives.Longs;
import com.imooc.xunwu.base.HouseSort;
import com.imooc.xunwu.base.RentValueBlock;
import com.imooc.xunwu.entity.House;
import com.imooc.xunwu.entity.HouseDetail;
import com.imooc.xunwu.entity.HouseTag;
import com.imooc.xunwu.entity.SupportAddress;
import com.imooc.xunwu.repository.HouseDetailRepository;
import com.imooc.xunwu.repository.HouseRepository;
import com.imooc.xunwu.repository.HouseTagRepository;
import com.imooc.xunwu.service.ServiceMultiResult;
import com.imooc.xunwu.service.ServiceResult;
import com.imooc.xunwu.service.house.IAddressService;
import com.imooc.xunwu.web.form.RentSearch;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: hyr
 * @Date: 2018/11/5 15:29
 * @Description:
 */
@Service
public class SearchServiceImpl implements ISearchService {

    private static final Logger logger = LoggerFactory.getLogger(ISearchService.class);

    private static final String INDEX_NAME = "xunwu";

    private static final String INDEX_TYPE = "house";

    private static final String INDEX_TOPIC = "house_build";


    @Autowired
    private HouseRepository houseRepository;

    @Autowired
    private HouseDetailRepository houseDetailRepository;

    @Autowired
    private HouseTagRepository tagRepository;

    @Autowired
    private ModelMapper modelMapper;

    @Autowired
    private TransportClient esClient;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private IAddressService addressService;

    @RabbitListener(queues=INDEX_TOPIC)    //监听器监听指定的Queue
    public void processC(String content) {
        try {
            HouseIndexMessage message = objectMapper.readValue(content, HouseIndexMessage.class);

            switch (message.getOperation()) {
                case HouseIndexMessage.INDEX:
                    this.createOrUpdateIndex(message);
                    break;
                case HouseIndexMessage.REMOVE:
                    this.removeIndex(message);
                    break;
                default:
                    logger.warn("Not support message content " + content);
                    break;
            }
        } catch (IOException e) {
            logger.error("Cannot parse json for " + content, e);
        }
    }


    private void createOrUpdateIndex(HouseIndexMessage message) {
        Long houseId = message.getHouseId();
        House house = houseRepository.findOne(houseId);
        if (house == null){
            logger.error("Index house {} dose not exist!",houseId);
            this.index(houseId,message.getRetry() + 1);
            return;
        }

        HouseIndexTemplate indexTemplate = new HouseIndexTemplate();
        modelMapper.map(house,indexTemplate);

        HouseDetail houseDetail = houseDetailRepository.findByHouseId(houseId);
        if (houseDetail == null){
            // TODO 异常情况
        }
        modelMapper.map(houseDetail,indexTemplate);

        List<HouseTag> tags = tagRepository.findAllByHouseId(houseId);
        if (tags != null && !tags.isEmpty()){
            List<String> tagStrings = new ArrayList<>();
            tags.forEach(houseTag -> tagStrings.add(houseTag.getName()));
            indexTemplate.setTags(tagStrings);
        }
        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME).setTypes(INDEX_TYPE)
                .setQuery(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID,houseId));
        logger.debug(requestBuilder.toString());
        SearchResponse searchResponse = requestBuilder.get();
        boolean success;
        long totaoHit = searchResponse.getHits().getTotalHits();
        if (totaoHit == 0){
            success = create(indexTemplate);
        }else if (totaoHit == 1){
            String esId = searchResponse.getHits().getAt(0).getId();
            success = update(esId,indexTemplate);
        }else {
            success = deleteAndCreate(totaoHit,indexTemplate);
        }

        if (success){
            logger.debug("index success");
        }

    }


    private void removeIndex(HouseIndexMessage message) {
        Long houseId = message.getHouseId();
        DeleteByQueryRequestBuilder  builder = DeleteByQueryAction.INSTANCE
                .newRequestBuilder(esClient)
                .filter(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID,houseId))
                .source(INDEX_NAME);

        logger.debug("Delete by query for house: " + builder);
        BulkByScrollResponse response = builder.get();
        long deleted = response.getDeleted();
        logger.debug("Delete total " + deleted);
        if (deleted <= 0){
            this.remove(houseId,message.getRetry() +1);
        }
    }

    private void remove(Long houseId, int retry) {
        if (retry > HouseIndexMessage.MAX_RETRY){
            logger.error("Retry remove times over 3 for house: "+ houseId + "please check it");
            return;
        }
        HouseIndexMessage message = new HouseIndexMessage(houseId,HouseIndexMessage.REMOVE,retry);
        try {
           this.amqpTemplate.convertAndSend(INDEX_TOPIC,objectMapper.writeValueAsString(message));
        } catch (JsonProcessingException e) {
            logger.error("Json encode error for "+ message);
        }

    }


    private void index(Long houseId, int retry) {
        if (retry > HouseIndexMessage.MAX_RETRY){
            logger.error("Retry index times over 3 for house: "+ houseId + "please check it!");
            return;
        }

        HouseIndexMessage message = new HouseIndexMessage(houseId,HouseIndexMessage.INDEX,retry);
        try {
            this.amqpTemplate.convertAndSend(INDEX_TOPIC,objectMapper.writeValueAsString(message));
        } catch (JsonProcessingException e) {
            logger.error("Json encode error for "+ message);
        }
    }

    /**
     * 索引目标房源
     *
     * @param houseId
     */
    @Override
    public void index(Long houseId) {
        this.index(houseId,0);
    }

    /**
     * 移除房源索引
     *
     * @param houseId
     */
    @Override
    public void remove(Long houseId) {
        remove(houseId,0);
    }

    private boolean create(HouseIndexTemplate indexTemplate){
        if(updateSuggest(indexTemplate)){

        }
        try {
            IndexResponse response = this.esClient.prepareIndex(INDEX_NAME,INDEX_TYPE)
                    .setSource(objectMapper.writeValueAsBytes(indexTemplate),XContentType.JSON).get();
            logger.debug("Create index with house:" + indexTemplate.getHouseId());
            if (response.status() == RestStatus.CREATED){
                return true;
            }else {
                return false;
            }
        } catch (JsonProcessingException e) {
            logger.error("Error to index house: " + indexTemplate.getHouseId(),e);
            return false;
        }
    }

    private boolean update(String esId, HouseIndexTemplate indexTemplate){
        try {
            UpdateResponse response = this.esClient.prepareUpdate(INDEX_NAME,INDEX_TYPE,esId)
                    .setDoc(objectMapper.writeValueAsBytes(indexTemplate),XContentType.JSON).get();
            logger.debug("update index with house:" + indexTemplate.getHouseId());
            if (response.status() == RestStatus.OK){
                return true;
            }else {
                return false;
            }
        } catch (JsonProcessingException e) {
            logger.error("Error to index house: " + indexTemplate.getHouseId(),e);
            return false;
        }
    }

    private boolean deleteAndCreate(long totalHit, HouseIndexTemplate indexTemplate){
        DeleteByQueryRequestBuilder  builder = DeleteByQueryAction.INSTANCE
                .newRequestBuilder(esClient)
                .filter(QueryBuilders.termQuery(HouseIndexKey.HOUSE_ID,indexTemplate.getHouseId()))
                .source(INDEX_NAME);

        logger.debug("Delete by query for house: " + builder);
        BulkByScrollResponse response = builder.get();
        long deleted = response.getDeleted();
        if (deleted != totalHit){
            logger.warn("Need delete {} ,but {} was deleted ",totalHit,deleted);
            return false;
        }else {
            return create(indexTemplate);
        }

    }


    /**
     * 查询房源接口
     * @param rentSearch
     * @return
     */
    @Override
    public ServiceMultiResult<Long> query(RentSearch rentSearch) {
        BoolQueryBuilder boolQuery = new BoolQueryBuilder();

        // 标题
        boolQuery.filter(
                QueryBuilders.termQuery(HouseIndexKey.CITY_EN_NAME,rentSearch.getCityEnName())
        );
        if (rentSearch.getRegionEnName() != null && !"*".equals(rentSearch.getRegionEnName())){
            boolQuery.filter(QueryBuilders.termQuery(HouseIndexKey.REGION_EN_NAME,rentSearch.getRegionEnName()));
        }
        // 面积
        RentValueBlock area = RentValueBlock.matchArea(rentSearch.getAreaBlock());
        if (!RentValueBlock.ALL.equals(area)){
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(HouseIndexKey.AREA);
            if (area.getMax() > 0){
                rangeQueryBuilder.lt(area.getMax());
            }
            if (area.getMin() > 0){
                rangeQueryBuilder.gte(area.getMin());
            }
            boolQuery.filter(rangeQueryBuilder);
        }
        //价格
        RentValueBlock price = RentValueBlock.matchArea(rentSearch.getPriceBlock());
        if (!RentValueBlock.ALL.equals(price)){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(HouseIndexKey.PRICE);
            if (price.getMax() > 0){
                rangeQuery.lt(price.getMax());
            }
            if (price.getMin() > 0){
                rangeQuery.gte(price.getMin());
            }
            boolQuery.filter(rangeQuery);
        }
        //朝向
        if(rentSearch.getDirection() > 0){
            boolQuery.filter(
                    QueryBuilders.termQuery(HouseIndexKey.DIRECTION,rentSearch.getDirection())
            );
        }
        //租住方式
        if (rentSearch.getRentWay() > -1){
            boolQuery.filter(
                    QueryBuilders.termQuery(HouseIndexKey.RENT_WAY,rentSearch.getRentWay())
            );
        }

        boolQuery.must(QueryBuilders.multiMatchQuery(
                rentSearch.getKeywords(),
                HouseIndexKey.TITLE,
                HouseIndexKey.TRAFFIC,
                HouseIndexKey.DISTRICT,
                HouseIndexKey.ROUND_SERVICE,
                HouseIndexKey.SUBWAY_LINE_NAME,
                HouseIndexKey.SUBWAY_STATION_NAME
        ));

        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
                .setTypes(INDEX_TYPE)
                .setQuery(boolQuery)
                .addSort(
                        HouseSort.getSortKey(rentSearch.getOrderBy()),
                        SortOrder.fromString(rentSearch.getOrderDirection())
                );
        logger.debug(requestBuilder.toString());
        List<Long> houseIds = new ArrayList<>();
        SearchResponse response = requestBuilder.get();
        if (response.status() != RestStatus.OK){
            logger.warn("Search status is no ok for " + requestBuilder);
            return new ServiceMultiResult<>(0,houseIds);
        }

        for (SearchHit hit : response.getHits()) {
            houseIds.add(Longs.tryParse(String.valueOf(hit.getSource().get(HouseIndexKey.HOUSE_ID))));
        }

        return new ServiceMultiResult<>(response.getHits().totalHits,houseIds);
    }

    /**
     * 搜索自动补全
     * @param content
     * @return
     */
    @Override
    public ServiceMultiResult<String> suggest(String content) {


        return null;
    }


    private boolean updateSuggest(HouseIndexTemplate houseIndexTemplate){
        return false;
    }
}
